﻿/* *****************************************************************************
ShoutcastRunner 0.3

Copyright (c) KEXP 90.3 FM Seattle. All rights reserved. 
www.kexp.org
author: Louis O'Callaghan (louis@kexp.org)
October 2012

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0  

THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
MERCHANTABLITY OR NON-INFRINGEMENT. 

See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.

SHOUTcast™ is a trademark of AOL LLC.
***************************************************************************** */

using System;
using System.Configuration;
using System.IO;
using System.Text;
using System.Threading;
using log4net;

namespace Kexp.ShoutcastRunner
{
	public static class Helpers
	{
		private static readonly ILog Log = LogManager.GetLogger(typeof(Helpers));

		// magic numbers
		private const int LockWaitLimit = 5000; // 5 secs

		public static string GetDatedFileName(string fileName, DateTime dateTime, bool includeTicks = false, bool monthly = false)
		{
			var builder = new StringBuilder();
			builder.AppendFormat("{0}-{1,2:D2}", dateTime.Year, dateTime.Month);
			if (!monthly)
				builder.AppendFormat("-{0,2:D2}", dateTime.Day);
			if (includeTicks)
				builder.AppendFormat("-{0}", dateTime.Ticks);
			builder.AppendFormat("_{0}", fileName);
			return builder.ToString();
		}

		/// <summary>
		/// If it's within an hour after midnight assume we want the previous day
		/// </summary>
		/// <returns></returns>
		public static DateTime GetAdjustedNowDate()
		{
			DateTime now = DateTime.Now;
			return now.Hour == 0 ? now.AddDays(-1) : now;
		}

        public static int? GetAppSettingAsInt(string key)
        {
            int output;
            string setting = ConfigurationManager.AppSettings[key];
            if (setting.IsNullOrEmptyOrWhiteSpace() || !Int32.TryParse(setting, out output))
                return null;

            return output;
        }

	    public static bool? GetAppSettingAsBool(string key)
	    {
	        bool output;
	        string setting = ConfigurationManager.AppSettings[key];
	        if (key.IsNullOrEmptyOrWhiteSpace() || !Boolean.TryParse(setting, out output))
	            return null;

	        return output;
	    }

	    public static int GetMillisecondsUntilMidnight()
		{
			var now = DateTime.Now;

			// add an extra second otherwise it gives 23:59
			var midnight = DateTime.Today.AddHours(24).AddSeconds(1);
			return (int)((midnight - now).TotalMilliseconds);
		}

		public static long GetMillisecondsUntilDayOfMonth(int dayOfMonth = 1)
		{
			var now = DateTime.Now;

			var nextMonth = new DateTime(now.Year, now.Month, dayOfMonth, 0, 0, 0);
			if (nextMonth < now)
				nextMonth = nextMonth.AddMonths(1);

			return (long) (nextMonth - now).TotalMilliseconds;
		}

		public static string GetTimelineFromMilliseconds(long ms)
		{
			int days = (int)(ms / (1000 * 60 * 60 * 24));
			ms = ms - (days * 1000 * 60 * 60 * 24);
			int hours = (int)ms / (1000 * 60 * 60);
			ms = ms - (hours * 60 * 60 * 1000);
			int mins = (int)ms / (1000 * 60);
			ms = ms - (mins * 1000 * 60);
			int secs = (int)ms / 1000;

			return String.Format("{0} days, {1} hours, {2} minutes, {3} seconds", 
				days, hours, mins, secs);
		}

		// from http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use
		public static bool IsFileLocked(this FileInfo file)
		{
			try
			{
				using (file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
				{ }
			}
			catch (IOException)
			{
				//the file is unavailable because it is:
				//still being written to
				//or being processed by another thread
				//or does not exist (has already been processed)
				return true;
			}

			//file is not locked
			return false;
		}

		public static bool IsNullOrEmptyOrWhiteSpace(this string value)
		{
			return (String.IsNullOrEmpty(value) || value.Trim().Length == 0);
		}

		public static bool IsW3CLog(this FileInfo logInfo)
		{
			// might actually try looking at the log format but this is easier
			return logInfo.Name.ToLower().IndexOf("w3c") >= 0;
		}

		public static void RotateLogFile(FileInfo logInfo, bool monthlyNaming)
		{
			if (logInfo == null)
				throw new ArgumentException("logInfo");

			if (!WaitForFileToUnlock(logInfo, LockWaitLimit))
				throw new Exception("Log file is locked and I can't wait forever.");

			// figure out what we should name this file. Don't overwrite existing files.
			DateTime namingDate = GetAdjustedNowDate();
			string directoryPath = logInfo.DirectoryName;
			string datedName = GetDatedFileName(logInfo.Name, namingDate, false, monthlyNaming);
			if (File.Exists(Path.Combine(directoryPath, datedName)))
				datedName = GetDatedFileName(logInfo.Name, namingDate, true, monthlyNaming);

			string copyPath = Path.Combine(directoryPath, datedName);
			Log.Debug(String.Format("Moving {0} to {1}", logInfo.FullName, copyPath));
			logInfo.CopyTo(copyPath);
			logInfo.Delete();
		}

		public static bool WaitForFileToUnlock(FileInfo file, int lockWaitLimit)
		{
			int seconds = 0;
			while (file.IsFileLocked())
			{
				Thread.Sleep(1000);
				seconds++;
				if (seconds * 1000 > lockWaitLimit)
					return false;
			}

			return true;
		}
	}
}
